package delta;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import ViewPackage.IFieldableView;
import ViewPackage.Main;

public class Mordor {
	/*
	 * NOT IMPLEMENTED class GraphPoint { public int id; public Road r; public
	 * int cost; }
	 */
	/* DELETE - Only for test purposes */
	class MyMap {
		int key;
		int value;

		MyMap(int key, int value) {
			this.key = key;
			this.value = value;
		}
	}

	/* A palya, es adatai */
	private Field[] Fields;
	
	//Az uj resz
	private Field[][] fields;
	private int[][] map;
	private int ticks=0;
	
	private int width;
	private int length;
	private int size;

	/* Az ellensegeket ezekre lehet lerakni */
	List<Road> startRoads;

	/* DELETE - Only for test purposes */
	ArrayList<MyMap> m;

	public Mordor(int width, int length, int roads) {
		initializeMordor(width, length);
		createRandomWorld(roads);
	}
	

	public Mordor(MapSize map) {
		initializeMordor(map.getWidth(), map.getLength());
		createRandomWorld(map.getRoadsCount());
	}

	public Mordor(int width, int length, String s) {
		initializeMordor(width, length);
		createStaticWorld(s);
	}

	private void initializeMordor(int width, int length) {
		this.width = width;
		this.length = length;
		size = width * length;

		Fields = new Field[size];
		startRoads = new ArrayList<Road>();

		/* Ez kell ide??? */
		for (int i = 0; i < size; i++)
			Fields[i] = null;
	}

	public void updateWorld() {
		if(Fields!=null)for (Field field : Fields) {
			field.update();
		}
		else if(fields!=null){
			for(int i=0;i<fields.length;i++){
				for(int j=0;j<fields[i].length;j++){
					fields[i][j].update();
				}
			}
			
			
		}
	}

	@Override
	public String toString() { // tesztelesi celokra
		StringBuilder str = new StringBuilder();
		for (Field field : Fields) {
			str.append(field);
		}
		return str.toString();
	}

	public Road getRandomStartRoad() {
		Random rand = new Random();
		return startRoads.get(rand.nextInt(startRoads.size()));
	}

	public void printMap(Boolean toFile) {
		if (toFile) {
			try {
				BufferedWriter output = new BufferedWriter(new FileWriter("output.txt", true));
				for (int i = 0; i < Fields.length; i++) {

					ArrayList<IFieldable> list = Fields[i].getObjects();

					String str = new String("");

					for (IFieldable o : list) {
						str = str + o.getMapchar();

					}
					if (str.contains("H")) {
						output.write("H");
					} else if (str.contains("D")) {
						output.write("D");
					} else if (str.contains("E")) {
						output.write("E");
					} else if (str.contains("F")) {
						output.write("F");
					} else if (str.contains("T")) {
						output.write("T");
					} else if (str.contains("O")) {
						output.write("H");
					} else {
						output.write("X");
					}
					if (((i + 1) % width) == 0)
						output.write("\n");
					;

				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	public Field getField(int columnIndex, int rowIndex) {
		Integer i = (rowIndex - 1) * width + columnIndex - 1;
		return Fields[i];
	}

	private void createRandomWorld(int roads) {
		// Initialize variables
		Random r = new Random();
		ArrayList<Integer> startPoints = new ArrayList<Integer>();
		ArrayList<Integer> nextPossibleFields = new ArrayList<Integer>();
		int c = 0; // Current road
		int p = 0; // Previous road
		int ratio = 8;
		/*
		 * RATIO TABLE - NEXT ROAD T - Towards, U/D - Up and down, *C - Connect
		 * up or down 2 > T: 50%, U/D: 50%, *C: 14% 4 > T: 66%, U/D: 33%, *C:
		 * 9.1% 8 > T: 80%, U/D: 20%, *C: 5.3% 18 > T: 90%, U/D: 10%, *C: 2.6%
		 */
		int ratioSquare = ratio * ratio;
		boolean isEnded = false;
		/* DELETE - only for test purposes */
		m = new ArrayList<MyMap>();

		// Determine startpoints - OK!
		int k = 0;
		for (int i = 0; i < width; i++) {
			startPoints.add(k);
			k += length;
		}

		// Loop for create world
		for (int i = 0; i < roads; i++) {
			// Set current road to active
			isEnded = false;

			// Select a random start point
			int tempStartPoint = r.nextInt(startPoints.size() - 1);
			c = startPoints.get(tempStartPoint); // Save position
			startPoints.remove(tempStartPoint); // Delete from list

			// Put down first road
			Fields[c] = new Road();
			startRoads.add((Road) Fields[c]);
			/* DELETE - only for test purposes */
			m.add(new MyMap(i, c));

			// Select next road
			p = c;
			c++;

			// Put down next road
			Fields[c] = new Road();
			((Road) Fields[p]).addNextRoad((Road) Fields[c]);
			/* DELETE - only for test purposes */
			m.add(new MyMap(i, c));

			// Select next random roads
			while (!isEnded) {
				// Check all possibilities first
				nextPossibleFields.clear();
				int up = c - length;
				int down = c + length;
				int towards = c + 1;
				/*
				 * NOT ALLOWED! int backwards = c - 1;
				 */

				// Direction UP
				if (up >= 0 && up != p) {
					if (Fields[up] == null)
						for (int a = 0; a < ratio; a++)
							nextPossibleFields.add(up);
					else
						for (int a = 0; a < ratio / 2; a++)
							nextPossibleFields.add(up);
				}
				// Direction DOWN
				if (down <= size && down != p) {
					if (Fields[down] == null)
						for (int a = 0; a < ratio; a++)
							nextPossibleFields.add(down);
					else
						for (int a = 0; a < ratio / 2; a++)
							nextPossibleFields.add(down);
				}
				// Direction TOWARDS

				if (towards % length != 0 && towards != p) {
					if (Fields[towards] == null)
						for (int a = 0; a < ratioSquare; a++)
							nextPossibleFields.add(towards);
				}
				/*
				 * NOT ALLOWED! // Direction BACKWARDS if (backwards %
				 * map.getLength() != 0 && backwards != p) { if
				 * (fields[backwards] == null) for (int a = 0; a < [NODATA];
				 * a++) nextPossibleFields.add(backwards); else
				 * nextPossibleFields.add(backwards); }
				 */

				// Select next random road
				p = c;
				c = nextPossibleFields.get(r.nextInt(nextPossibleFields.size() - 1));

				// Place new road or connect to another
				// If road is at the end
				if (towards % length == 0) {
					((Road) Fields[p]).addNextRoad(null);
					isEnded = true; // End of current road
				} else if (Fields[c] == null) {
					Fields[c] = new Road(); // If it's only a field, place new
											// road
					((Road) Fields[p]).addNextRoad((Road) Fields[c]);
					/* DELETE - only for test purposes */
					m.add(new MyMap(i, c));
				} else {
					((Road) Fields[p]).addNextRoad((Road) Fields[c]);
					isEnded = true; // End of current road
					m.add(new MyMap(i, c));
				}
			}
		}

		// Fill up null with Field
		for (int i = 0; i < size; i++)
			if (Fields[i] == null)
				Fields[i] = new Field();

		connectFields();
		calculateDistance();
	}

	private void createStaticWorld(String s) {
		for (int i = 0; i < size; i++)
			Fields[i] = new Road();

		for (int i = 0; i < size; i++) {
			String c = String.valueOf(s.charAt(i * 2)) + String.valueOf(s.charAt(i * 2 + 1));
			if (c.equals("M0")) {
				Fields[i] = new Field();
			} else if (c.equals("UB")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - 1]);
			} else if (c.equals("UJ")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i + 1]);
			} else if (c.equals("UF")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - length]);
			} else if (c.equals("UL")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i + length]);
			} else if (c.equals("UK")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - 1]);
				startRoads.add((Road) Fields[i]);
			} else if (c.equals("UV")) {
				((Road) Fields[i]).addNextRoad(null);
			} else if (c.equals("K1")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - length]);
				((Road) Fields[i]).addNextRoad((Road) Fields[i + length]);
			} else if (c.equals("K2")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - length]);
				((Road) Fields[i]).addNextRoad((Road) Fields[i - 1]);
			} else if (c.equals("K3")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - 1]);
				((Road) Fields[i]).addNextRoad((Road) Fields[i + length]);
			} else if (c.equals("K4")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - 1]);
				((Road) Fields[i]).addNextRoad((Road) Fields[i + length]);
				((Road) Fields[i]).addNextRoad((Road) Fields[i - length]);
			} else if (c.equals("K5")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - length]);
			} else if (c.equals("K6")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i - 1]);
			} else if (c.equals("K7")) {
				((Road) Fields[i]).addNextRoad((Road) Fields[i + length]);
			}
		}
		connectFields();
		calculateDistance();
	}

	private void connectFields() {
		for (int i = 0; i < size; i++) {
			int up = i - length;
			int down = i + length;
			int towards = i + 1;
			int backwards = i - 1;

			if (up >= 0)
				Fields[i].connect(Fields[up]);
			else
				Fields[i].connect(null);

			if (down < width * length)
				Fields[i].connect(Fields[down]);
			else
				Fields[i].connect(null);

			if (towards % length != 0)
				Fields[i].connect(Fields[towards]);
			else
				Fields[i].connect(null);

			if (i % length != 0)
				Fields[i].connect(Fields[backwards]);
			else
				Fields[i].connect(null);
		}
	}

	private void calculateDistance() {
		/* NOT IMPLEMENTED! */
		for (int i = 0; i < size; i++)
			if (Fields[i].getClass().getCanonicalName().contains("Road"))
				((Road) Fields[i]).setDistance(1);

		/*
		 * NOT IMPLEMENTED int counter = 1; ArrayList<Road> stack = new
		 * ArrayList<Road>(); ArrayList<Graph> points = new ArrayList<Graph>();
		 * for (Road r : startRoads) stack.add(r);
		 * 
		 * for (Road r : startRoads) { points.add(new Graph(r, counter,0));
		 * counter++; }
		 * 
		 * while (stack.get(0) != null) { Road c = stack.get(0);
		 * stack.remove(0); while (stack.get(0).getNextRoad(0) != null) { if
		 * (stack.get(0).getNextRoads().size() > 1) { for (int i = 1; i <
		 * stack.get(0).getNextRoads().size(); i++)
		 * stack.add(stack.get(0).getNextRoad(i)); points.add(new
		 * Graph(stack.get(0), counter,)); }
		 * 
		 * } }
		 */
	}

	/* DELETE - Only for test purposes */
	public void printWorld() {
		Integer breakpoint = 1;
		for (Integer i = 0; i < size; i++) {
			for (MyMap k : m) {
				if (i == k.value) {
					System.out.print(k.key + " ");
					break;
				}
			}
			if (Fields[i].getClass().getCanonicalName().contains("Field")) {
				System.out.print("- ");
			}
			if (breakpoint % length == 0) {
				System.out.println();
			}
			breakpoint++;
		}
	}
	
	//=====================================================================
	//Az ujonnan hozzairt dolgok
	//=====================================================================
	
	//Az atadott map matrix alapjan(amiben 1 az ut , 0 a mezo)
		//letrehoz egy uj palyat
		//a map -nak ervenyesnek kell lennie
		public Mordor(int[][] map){
			this.map=map;
			Fields=null;
			if(map!=null){
				fields=makeAMap();
				startRoads=new ArrayList<Road>();
				for(int i=0;i<fields.length;i++){
				
					if(fields[i][0].getDistance()!=-1){
						Road rd=(Road)fields[i][0];
						startRoads.add(rd);
					}
				}
				
			}
			
		}
	
	//Visszater a palyaval
	public Field[][] getFields(){
		return fields;
	}
	
	
	//A mordor map attributuma alapjan letrehoz egy palyat
	public Field[][] makeAMap(){
		
		
		Field[][] fields=new Field[map.length][map[0].length];
		
		for(int i=0;i<fields.length;i++){
			for(int j=0;j<fields[i].length;j++){
				if(map[i][j]==0)fields[i][j]=new Field();
				else fields[i][j]=new Road();
				
			}
		}
		for(int i=0;i<fields.length;i++){
			for(int j=0;j<fields[i].length;j++){
				if(i>0 && j>0 )fields[i][j].connect(fields[i-1][j-1]);
				if(i>0)fields[i][j].connect(fields[i-1][j]);
				if(i>0 && j<fields[i].length-1 )fields[i][j].connect(fields[i-1][j+1]);
				if(j>0 )fields[i][j].connect(fields[i][j-1]);
				
				if(j<fields[i].length-1)fields[i][j].connect(fields[i][j+1]);
				if(i<fields.length-1 && j>0 )fields[i][j].connect(fields[i+1][j-1]);
				if(i<fields.length-1 )fields[i][j].connect(fields[i+1][j]);
				if(i<fields.length-1 && j<fields[i].length-1 )fields[i][j].connect(fields[i+1][j+1]);
				
				
			}
		}
		
		for(int i=0;i<map.length;i++){
			for(int j=0;j<map[i].length;j++){
				if(map[i][j]==1){
					if(i>0){
						fields[i][j].connect(fields[i-1][j]);
					}
					if(j>0 ){
						fields[i][j].connect(fields[i][j-1]);
					}
					if(j<fields[i].length-1){
						fields[i][j].connect(fields[i][j+1]);
					}
		
					if(i<fields.length-1 ){
						fields[i][j].connect(fields[i+1][j]);
					}
				
				}
			}
		}
		
		for(int i=0;i<map.length;i++){
			if(map[i][map[i].length-1]!=0)rekursiveAddRoad(fields,i,map[i].length-1,-3,-3,1);
		}
		
		
		return fields;
	}
	//Utkereso algoritmus ,mely a letrehoztott map integer matrix
		//es a fields attributum alapjan
		//osszekoti a fieldsben talalhato utakat egymassal es beallitja a tavolsagokat is
		//Fontos, hogy csak egy atadott veg -ut alapjan tudja visszakeresni az onnan 
		//elerheto kezdoutakat
		//Az is Fontos ,hogy a fields meg kell hogy feleleljen a map-nak (a fields es a map merete egyezik)
		//Tovabba ahol a mapban 1 van ott a fields -ben road, mig ahol a mapban 0 ott a fields -ben field 
		/*
		 * i,j: A vegut koordinataja
		 * prewi, prewj: az elozo ut koordinatai(mivel rekurziv)
		 * a fuggveny hivasakor a terminator -3 ertekre kell beallitani mindkettot
		 * distance: Tavolsag a vegzet hegyetol. Hivaskor 1
		 * 
		 * Visszater true -val ha sikerul utat talalni
		 */
		public boolean rekursiveAddRoad(Field[][] fields,int i,int j,int prewi,int prewj,int dist){
			
			//Ha az algoritmus eljut a palya bal szeleig, sikerult utat talalni
			if(j<0)return true;
			//Ha nem a palya bal szelere juttot de kiment a palyarol nincs ut
			if(i>=map.length || i<0 || j>=map[i].length)return false;
			
			//A terkepen 0 jeloli a field -et, nem 0 az utat
			//-2 jeloli az olyan utat amin az algoritmus jart mar
			//Ez a sor biztositja, hogy az algoritmus csak nem jart helyre menjen(ne keletkezzen hurok)
			if(map[i][j]==0 || map[i][j]==-2)return false;
			
			//Ha "lephet" a fuggveny az i,j helyre, akkor allitgatni fogja a tavolsagot is
			//Ehhez elmentodik a korabbi tavolsag
			//Ez -2 mar nem lehet, de 1 meg igen. Ha a mapra 1 van irva akkor az mentodik el
			//Az algoritmus a futas soran az utak helyeire a map matrixon tavolsagokat tesz
			int oldDistance=map[i][j];
			
			
			//Egy adott uton 4 iranyba lehet menni. A negy valtozo a sikeres tovabblepest jeloli
			boolean result[]=new boolean[]{false,false,false,false};
			
			//Annak jelzese hogy az ut mar jart(a tavolsag kitorlodik, de visszatoltheto az oldDistance -bol)
			//Teszt celjabol az uton is beallitja
			map[i][j]=-2;
			((Road)fields[i][j]).setDistance(-2);
			
			//Az elso irany
			if(!(i-1==prewi && j==prewj)){
				//megprobal utat hozzadni(ezt a fuggveny megteszi)
				//Ha sikerult(el lehet jutni a palya bal szelere), a fuggveny true-val jelzi
				//A tavolsag eggyel nagyobb a hivott fuggvenyben
				result[0]=rekursiveAddRoad(fields,i-1, j,i,j,dist+1);
					
		
				if(result[0]){
					//Ha van ut, be lehet allitani tavolsagot is
					//de csak akkor, ha az uj tavolsag kisebb mint az elozo
					//vagy az alapertelmezett ertek van beallitva
					if(oldDistance>=dist || oldDistance==1){
						
						map[i][j]=dist;
						((Road)fields[i][j]).setDistance(dist);
					}
					else{
						map[i][j]=oldDistance;
						((Road)fields[i][j]).setDistance(oldDistance);
					}
						
				}
			}
				
				if(!(i==prewi && j+1==prewj)){
					//megprobal utat hozzadni(ezt a fuggveny megteszi)
					//Ha sikerult(el lehet jutni a palya bal szelere), a fuggveny true-val jelzi
					//A tavolsag eggyel nagyobb a hivott fuggvenyben
					result[1]=rekursiveAddRoad(fields,i,j+1,i,j,dist+1);
					
					if(result[1]){
						//Ha van ut, be lehet allitani tavolsagot is
						//de csak akkor, ha az uj tavolsag kisebb mint az elozo
						//vagy az alapertelmezett ertek van beallitva
						if(oldDistance>=dist || oldDistance==1){
							
							map[i][j]=dist;
							((Road)fields[i][j]).setDistance(dist);
						}
						else{
							map[i][j]=oldDistance;
							((Road)fields[i][j]).setDistance(oldDistance);
						}
						
					}
				}
			
				if(!(i+1==prewi && j==prewj)){
					//megprobal utat hozzadni(ezt a fuggveny megteszi)
					//Ha sikerult(el lehet jutni a palya bal szelere), a fuggveny true-val jelzi
					//A tavolsag eggyel nagyobb a hivott fuggvenyben
					result[2]=rekursiveAddRoad(fields,i+1, j,i,j,dist+1);
					
					if(result[2]){
						//Ha van ut, be lehet allitani tavolsagot is
						//de csak akkor, ha az uj tavolsag kisebb mint az elozo
						//vagy az alapertelmezett ertek van beallitva
						if(oldDistance>=dist || oldDistance==1){
							
							map[i][j]=dist;
							((Road)fields[i][j]).setDistance(dist);
						}
						else{
							map[i][j]=oldDistance;
							((Road)fields[i][j]).setDistance(oldDistance);
						}

					}
				}
			
				if(!(i==prewi && j-1==prewj)){
					//megprobal utat hozzadni(ezt a fuggveny megteszi)
					//Ha sikerult(el lehet jutni a palya bal szelere), a fuggveny true-val jelzi
					//A tavolsag eggyel nagyobb a hivott fuggvenyben
					result[3]=rekursiveAddRoad(fields,i, j-1,i,j,dist+1);
					
					if(result[3]){
						//Ha van ut, be lehet allitani tavolsagot is
						//de csak akkor, ha az uj tavolsag kisebb mint az elozo
						//vagy az alapertelmezett(1) ertek van beallitva
						if(oldDistance>=dist || oldDistance==1){
							map[i][j]=dist;
							((Road)fields[i][j]).setDistance(dist);
						}
						else{
							map[i][j]=oldDistance;
							((Road)fields[i][j]).setDistance(oldDistance);
						}
						
					}
				}
			
			
			//A fuggveny akkor terhet vissza true -val, ha valamelyik iranyba sikerult tovabbmenni
			boolean res= result[0] || result[1] || result[2] || result[3];
			
			//Ha nem sikerult tovabbmenni, akkor a map -on most -2 van
			//vissza kell irni az eredeti ertekbe
			if(!res){
				map[i][j]=oldDistance;
				((Road)fields[i][j]).setDistance(oldDistance);
			}
			//Ha sikerult tovabbmenni akkor az ut osszekotheto az elozovel
			else if(prewj!=-3 && prewi!=-3){
				Road rd=(Road)fields[i][j];
				rd.addNextRoad(((Road)fields[prewi][prewj]));
			}
			
			return res;
		}
		
}
